package com.szzg10.webservice.chapter4;

import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.annotation.Resource;
import javax.xml.namespace.NamespaceContext;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.http.HTTPException;
import javax.xml.ws.BindingType;
import javax.xml.ws.Provider;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceProvider;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

@WebServiceProvider
@ServiceMode(value = Mode.MESSAGE)
@BindingType(value = HTTPBinding.HTTP_BINDING)
public class RabbitCountProvider implements Provider<Source> {

	@Resource
	private WebServiceContext serviceContext;
	
	private Map<Integer, Integer> map = new HashMap<Integer, Integer>();
	
	private static final String xml_start = "<fib:response xmlns:fib='urn:fib'>";
	private static final String xml_end = "</fib:response>";
	
	@Override
	public Source invoke(Source source) {
		
		MessageContext ctx = serviceContext.getMessageContext();
		
		String method = (String)ctx.get(MessageContext.HTTP_REQUEST_METHOD);
		if(method.equalsIgnoreCase("POST")) {
			return doPost(source);
		}
		else if(method.equalsIgnoreCase("GET")) {
			return doGet(source);
		}
		else if(method.equalsIgnoreCase("DELETE")) {
			return doDelete(source);
		}
		else {
			throw new HTTPException(405);
		}
	}
	
	private Source doPost(Source source) {
		String request_string = extract_source(source);
		request_string = request_string.substring(1);
		request_string = request_string.substring(0, request_string.length() - 1);
		
		String[] strs = request_string.split(", ");
		for(int i = 0; i < strs.length; i++) {
			int n = Integer.parseInt(strs[i]);
			map.put(n, countRabbits(n));
		}
		
		String result = xml_start + map.values().toString() + xml_end;
		return makeStreamSource(result);
	}
	
	private Source doGet(Source source) {
		String result = xml_start + map.values().toString() + xml_end;
		return makeStreamSource(result);
	}
	
	private Source doDelete(Source source) {
		map.clear();
		String result = xml_start + "Map is cleared." + xml_end;
		
		return makeStreamSource(result);
	}
	
	private String extract_source(Source source) {
		
		String request_string = "";
		
		DOMResult domResult = new DOMResult();
		try {
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.transform(source, domResult);
			
			XPath xPath = XPathFactory.newInstance().newXPath();
			xPath.setNamespaceContext(new NamespaceContext() {
				
				@Override
				public Iterator getPrefixes(String arg0) {
					return null;
				}
				
				@Override
				public String getPrefix(String arg0) {
					return "urn:fib";
				}
				
				@Override
				public String getNamespaceURI(String arg0) {
					return "urn:fib";
				}
			});
			
			request_string = xPath.evaluate("fib:request", domResult.getNode());
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		
		return request_string;
	}
	
	private int countRabbits(int n) {
		if(n < 0) {
			throw new HTTPException(400);
		}
		else if(n < 2) {
			return n;
		}
		else if(map.containsKey(n)) {
			return map.get(n);
		}
		else if(map.containsKey(n - 1) && map.containsKey(n - 2)) {
			int result = map.get(n - 1) + map.get(n - 2);
			map.put(n, result);
			
			return result;
		}
		else {
			int fib = 1;
			int prev = 0;
			for(int i = 2; i <= n; i++) {
				int temp = fib;
				fib += prev;
				prev = temp;
			}
			
			map.put(n, fib);
			
			return fib;
		}
		
	}
	
	private StreamSource makeStreamSource(String msg) {
		System.out.println(msg);
		
		ByteArrayInputStream in = new ByteArrayInputStream(msg.getBytes());
		return new StreamSource(in);
	}

}
